/*
	Class: Profiler
	
		This class allows you to log the execution time of functions. A profiler 
		can contain multiple timers which each monitor the performance of a 
		different function.

		Use this class to profile code sections. As an example, here is an instrumented 
		internal function:
		
			internal function redraw ( force : Boolean )
			{
				if ( ! force && ! needsRedraw )
				{
					return;
				}
				
		Profiler.getInstance ( ).enabled = true;
		Profiler.getInstance ( ).resetTimers ( );
		Profiler.getInstance ( ).startTimer ( "redraw" );
				// Discard the current containers and start again.
				//
				
		Profiler.getInstance ( ).startTimer ( "remove" );
				while ( containers.length )
				{
					containers [ 0 ].remove ( );
				}
		Profiler.getInstance ( ).stopTimer ( "remove" );
				
		Profiler.getInstance ( ).startTimer ( "draw" );
				draw ( );
		Profiler.getInstance ( ).stopTimer ( "draw" );
				
		Profiler.getInstance ( ).startTimer ( "renderChildren" );
				renderChildren ( );
		Profiler.getInstance ( ).stopTimer ( "renderChildren" );
				
				// By default we've disabled all of the onRenderContainer signals sent
				// by our many, many children for performance reasons so we finish up by 
				// sending this signal.
				//
				
		Profiler.getInstance ( ).startTimer ( "notifyOnRenderContainer" );
				notifyOnRenderContainer ( );
		Profiler.getInstance ( ).stopTimer ( "notifyOnRenderContainer" );
		Profiler.getInstance ( ).stopTimer ( "redraw" );
		Profiler.getInstance ( ).enabled = false;
		
				needsRedraw = false;
			}
		
		To display the results, call logTimers ( ).
*/

package com.frontalcode 
{
	import flash.events.*;
	import flash.utils.*;
	
    public class Profiler extends OptionableObject
    {
		private static var _inst:Profiler = null;
		
		private static var dataColumnsCount:Number = 7;
		private var perfCounterOrder:Array;
		private var perfCounters:Object;
		private var perfTimers:Object;
		private var perfExclusiveTimers:Object;
		private var perfRunningTimers:Object;
		private var perfTotals:Object;
		private var perfExclusiveLapsedTime:Object;
		private var perfExclusiveTotals:Object;
		private var perfMaxLapsedTimes:Object;
		private var displayColumnWidths:Array;
		private var _enabled:Boolean;
		
		/*
			Constructor: Profiler
			
				Initializes a profiler.
		*/
		public function Profiler ( options : Object = null )
		{
			super ( options );
			
			enabled = true;
			
			displayColumnWidths = new Array ( 0, 0, 0, 0, 0, 0 );
			
			this.resetTimers ( );
		}
		
		public function set enabled ( enable:Boolean ) : void 
		{
			this._enabled = enable;
		}
		
		public function get enabled ( ) : Boolean 
		{
			return this._enabled;
		}
		
		
		/*
			Function: resetTimers
				
				If enabled, resets this object's counters and timers. If this 
				object is not enabled, does nothing.
		*/		
		public function resetTimers ( ) : void
		{
			 if ( ! this._enabled )
			 {
				 return;
			 }
			 
			this.perfCounterOrder = new Array ( );
			this.perfCounters = new Object ( );
			this.perfTimers = new Object ( );
			this.perfRunningTimers = new Object ( );
			this.perfTotals = new Object ( );
			this.perfExclusiveLapsedTime = new Object ( );
			this.perfExclusiveTotals = new Object ( );
			this.perfMaxLapsedTimes = new Object ( );
		}
		
		/*
			Function: startTimer
			
				Starts a timer. If none of supplied name exists, creates a new timer with given name.
				
			Parameters:
			
				name = Name of timer.		
		*/
		public function startTimer ( name:String ) : void
		{
			 if ( ! this._enabled )
			 {
				 return;
			 }
			 
			var newTimer : Boolean = true;
			
			for ( var i:uint = 0; i < perfCounterOrder.length; i++ )
			{
				if ( perfCounterOrder [ i ] == name )
				{
					newTimer = false;
					break;
				}
			}
			
			if ( newTimer )
			{
				perfCounterOrder.push ( name );
			}
			
			if ( perfRunningTimers [ name ] == null ) perfRunningTimers [ name ] = 0;
			perfRunningTimers [ name ] += 1;
			if ( perfRunningTimers [ name ] == 1 ) perfTimers [ name ] = getTimer ( );
			
			if ( perfCounters [ name ] == null || perfCounters [ name ] == undefined )
			{
				perfCounters [ name ] = 0;
				perfTotals [ name ] = 0;
				perfExclusiveTotals [ name ] = 0;
				perfExclusiveLapsedTime [ name ] = 0;
				perfMaxLapsedTimes [ name ] = 0;
			}
		}
		
		/*
			Function: stopTimer
			
				Stop timer of given name.
				
			Parameter:
			
				name - Name of timer.
		*/
		public function stopTimer ( name:String ) : void
		{
			 if ( ! this._enabled )
			 {
				 return;
			 }
			 
			perfRunningTimers [ name ] -= 1;
			
			if ( perfRunningTimers [ name ] == 0 )
			{
				var lapsedTime:Number = getTimer ( ) - perfTimers [ name ];
				perfTotals [ name ] += lapsedTime;
				perfExclusiveLapsedTime [ name ] += lapsedTime;
				perfExclusiveTotals [ name ] += perfExclusiveLapsedTime [ name ];
				perfCounters [ name ] += 1;
				
				if ( lapsedTime > perfMaxLapsedTimes [ name ] )
				{
					perfMaxLapsedTimes [ name ] = lapsedTime;
				}
				
				// Remove our exclusive lapsed time from any running timers.
				//
				
				for ( var i:uint = 0; i < perfCounterOrder.length; i++ )
				{
					var thisName : String = perfCounterOrder [ i ];
				
					if ( perfRunningTimers [ thisName ] > 0 )
					{
						perfExclusiveLapsedTime [ thisName ] -= perfExclusiveLapsedTime [ name ];
					}
				}
				
				perfExclusiveLapsedTime [ name ] = 0;
			}
		}
		
		/*
			Function: logTimers
			
				Get the profiler results as a string.
		*/
		public function logTimers ( ) : String
		{
			var result : String = "";
			
			for ( var i : uint = 0; i < perfCounterOrder.length; i++ )
			{
				for ( var j : uint = 0; j < dataColumnsCount; j++ )
				{
					var label : String = '             ';
					var fieldName : String = getFieldName ( j ) + ':';
					label = fieldName + label.substr ( fieldName.length - 1 );
					result += label + getFieldValue ( j, perfCounterOrder [ i ] ) + "\n";
				}
				
				result += "\n";
			}
			
			return result;
		}
		
		internal function getFieldName ( index : Number ) : String
		{
			var fieldName : String = '';
			
			switch ( index )
			{
				case 0:
					fieldName = "NAME";
					break;
				case 1:
					fieldName = "TOTAL";
					break;
				case 2:
					fieldName = "AVG.";
					break;
				case 3:
					fieldName = "MAX.";
					break;
				case 4:
					fieldName = "EXCL. TOTAL";
					break;
				case 5:
					fieldName = "EXCL. AVG.";
					break;
				case 6:
					fieldName = "CALLS";
					break;
			}
					
			return fieldName;
		}
		
		internal function getFieldValue ( index : Number, name : String ) : *
		{
			var value : * = null;
			
			switch ( index )
			{
				case 0:
					value = name;
					break;
				case 1:
					value = perfTotals [ name ];
					break;
				case 2:
					value = perfCounters [ name ] == 0 ? 0 : perfTotals [ name ] / perfCounters [ name ];
					break;
				case 3:
					value = perfMaxLapsedTimes [ name ];
					break;
				case 4:
					value = perfExclusiveTotals [ name ];
					break;
				case 5:
					value = ( perfCounters [ name ] == 0 ? 0 : perfExclusiveTotals [ name ] / perfCounters [ name ] );
					break;
				case 6:
					value = perfCounters [ name ];
					break;
			}
					
			return value;
		}
		
		/*
			Function: getInstance
			
				Supplies the current active instance of Profiler.
				
			Returns:
			
				An instance of Profiler.
		*/
		static public function getInstance ( ) : Profiler
		{
			if ( _inst == null )
			{
				_inst = new Profiler ( );
			}
			
			return _inst;
		}
		
		// Convenience method.
		//
		static public function gI ( ) : Profiler { return getInstance ( ); }
	}
}

